home *** CD-ROM | disk | FTP | other *** search
- ;;; -*- Package: C; Log: C.Log -*-
- ;;;
- ;;; **********************************************************************
- ;;; This code was written as part of the CMU Common Lisp project at
- ;;; Carnegie Mellon University, and has been placed in the public domain.
- ;;; If you want to use this code or any part of CMU Common Lisp, please contact
- ;;; Scott Fahlman or slisp-group@cs.cmu.edu.
- ;;;
- (ext:file-comment
- "$Header: stack.lisp,v 1.5 91/05/08 01:15:34 ram Exp $")
- ;;;
- ;;; **********************************************************************
- ;;;
- ;;; The stack analysis phase in the compiler. We do a graph walk to
- ;;; determine which unknown-values continuations are on the stack at each point
- ;;; in the program, and then we insert cleanup code to pop off unused values.
- ;;;
- ;;; Written by Rob MacLachlan
- ;;;
- (in-package 'c)
-
-
- ;;; Find-Pushed-Continuations -- Internal
- ;;;
- ;;; Scan through Block looking for uses of :Unknown continuations that have
- ;;; their Dest outside of the block. We do some checking to verify the
- ;;; invariant that all pushes come after the last pop.
- ;;;
- (defun find-pushed-continuations (block)
- (let* ((2block (block-info block))
- (popped (ir2-block-popped 2block))
- (last-pop (if popped
- (continuation-dest (car (last popped)))
- nil)))
- (collect ((pushed))
- (let ((saw-last nil))
- (do-nodes (node cont block)
- (when (eq node last-pop)
- (setq saw-last t))
-
- (let ((dest (continuation-dest cont))
- (2cont (continuation-info cont)))
- (when (and dest
- (not (eq (node-block dest) block))
- 2cont
- (eq (ir2-continuation-kind 2cont) :unknown))
- (assert (or saw-last (not last-pop)))
- (pushed cont)))))
-
- (setf (ir2-block-pushed 2block) (pushed))))
- (undefined-value))
-
-
- ;;;; Annotation graph walk:
-
- ;;; Stack-Simulation-Walk -- Internal
- ;;;
- ;;; Do a backward walk in the flow graph simulating the run-time stack of
- ;;; unknown-values continuations and annotating the blocks with the result.
- ;;;
- ;;; Block is the block that is currently being walked and Stack is the stack
- ;;; of unknown-values continuations in effect immediately after block. We
- ;;; simulate the stack by popping off the unknown-values generated by this
- ;;; block (if any) and pushing the continuations for values received by this
- ;;; block. (The role of push and pop are interchanged because we are doing a
- ;;; backward walk.)
- ;;;
- ;;; If we run into a values generator whose continuation isn't on stack top,
- ;;; then the receiver hasn't yet been reached on any walk to this use. In this
- ;;; case, we ignore the push for now, counting on Annotate-Dead-Values to clean
- ;;; it up if we discover that it isn't reachable at all.
- ;;;
- ;;; If our final stack isn't empty, then we walk all the predecessor blocks
- ;;; that don't have all the continuations that we have on our Start-Stack on
- ;;; their End-Stack. This is our termination condition for the graph walk. We
- ;;; put the test around the recursive call so that the initial call to this
- ;;; function will do something even though there isn't initially anything on
- ;;; the stack.
- ;;;
- ;;; We can use the tailp test, since the only time we want to bottom out
- ;;; with a non-empty stack is when we intersect with another path from the same
- ;;; top-level call to this function that has more values receivers on that
- ;;; path. When we bottom out in this way, we are counting on
- ;;; DISCARD-UNUSED-VALUES doing its thing.
- ;;;
- ;;; When we do recurse, we check that predecessor's END-STACK is a
- ;;; subsequence of our START-STACK. There may be extra stuff on the top
- ;;; of our stack because the last path to the predecessor may have discarded
- ;;; some values that we use. There may be extra stuff on the bottom of our
- ;;; stack because this walk may be from a values receiver whose lifetime
- ;;; encloses that of the previous walk.
- ;;;
- ;;; If a predecessor block is the component head, then it must be the case
- ;;; that this is a NLX entry stub. If so, we just stop our walk, since the
- ;;; stack at the exit point doesn't have anything to do with our stack.
- ;;;
- (defun stack-simulation-walk (block stack)
- (declare (type cblock block) (list stack))
- (let ((2block (block-info block)))
- (setf (ir2-block-end-stack 2block) stack)
- (let ((new-stack stack))
- (dolist (push (reverse (ir2-block-pushed 2block)))
- (if (eq (car new-stack) push)
- (pop new-stack)
- (assert (not (member push new-stack)))))
-
- (dolist (pop (reverse (ir2-block-popped 2block)))
- (push pop new-stack))
-
- (setf (ir2-block-start-stack 2block) new-stack)
-
- (when new-stack
- (dolist (pred (block-pred block))
- (if (eq pred (component-head (block-component block)))
- (assert (find block
- (environment-nlx-info (block-environment block))
- :key #'nlx-info-target))
- (let ((pred-stack (ir2-block-end-stack (block-info pred))))
- (unless (tailp new-stack pred-stack)
- (assert (search pred-stack new-stack))
- (stack-simulation-walk pred new-stack))))))))
-
- (undefined-value))
-
-
- ;;; Annotate-Dead-Values -- Internal
- ;;;
- ;;; Do stack annotation for any values generators in Block that were
- ;;; unreached by all walks (i.e. the continuation isn't live at the point that
- ;;; it is generated.) This will only happen when the values receiver cannot be
- ;;; reached from this particular generator (due to an unconditional control
- ;;; transfer.)
- ;;;
- ;;; What we do is push on the End-Stack all continuations in Pushed that
- ;;; aren't already present in the End-Stack. When we find any pushed
- ;;; continuation that isn't live, it must be the case that all continuations
- ;;; pushed after (on top of) it aren't live.
- ;;;
- ;;; If we see a pushed continuation that is the CONT of a tail call, then we
- ;;; ignore it, since the tail call didn't actually push anything. The tail
- ;;; call must always the last in the block.
- ;;;
- (defun annotate-dead-values (block)
- (declare (type cblock block))
- (let* ((2block (block-info block))
- (stack (ir2-block-end-stack 2block))
- (last (block-last block))
- (tailp-cont (if (node-tail-p last) (node-cont last))))
- (do ((pushes (ir2-block-pushed 2block) (rest pushes))
- (popping nil))
- ((null pushes))
- (let ((push (first pushes)))
- (cond ((member push stack)
- (assert (not popping)))
- ((eq push tailp-cont)
- (assert (null (rest pushes))))
- (t
- (push push (ir2-block-end-stack 2block))
- (setq popping t))))))
-
- (undefined-value))
-
-
- ;;; Discard-Unused-Values -- Internal
- ;;;
- ;;; Called when we discover that the stack-top unknown-values continuation
- ;;; at the end of Block1 is different from that at the start of Block2 (its
- ;;; successor.)
- ;;;
- ;;; We insert a call to a funny function in a new cleanup block introduced
- ;;; between Block1 and Block2. Since control analysis and LTN have already
- ;;; run, we must do make an IR2 block, then do ADD-TO-EMIT-ORDER and
- ;;; LTN-ANALYZE-BLOCK on the new block. The new block is inserted after Block1
- ;;; in the emit order.
- ;;;
- ;;; If the control transfer between Block1 and Block2 represents a
- ;;; tail-recursive return (:Deleted IR2-continuation) or a non-local exit, then
- ;;; the cleanup code will never actually be executed. It doesn't seem to be
- ;;; worth the risk of trying to optimize this, since this rarely happens and
- ;;; wastes only space.
- ;;;
- (defun discard-unused-values (block1 block2)
- (declare (type cblock block1 block2))
- (let* ((block1-stack (ir2-block-end-stack (block-info block1)))
- (block2-stack (ir2-block-start-stack (block-info block2)))
- (last-popped (elt block1-stack
- (- (length block1-stack)
- (length block2-stack)
- 1))))
- (assert (tailp block2-stack block1-stack))
-
- (let* ((block (insert-cleanup-code block1 block2
- (continuation-next (block-start block2))
- `(%pop-values ',last-popped)))
- (2block (make-ir2-block block)))
- (setf (block-info block) 2block)
- (add-to-emit-order 2block (block-info block1))
- (ltn-analyze-block block)))
-
- (undefined-value))
-
-
- ;;;; Stack analyze:
-
-
- ;;; FIND-VALUES-GENERATORS -- Internal
- ;;;
- ;;; Return a list of all the blocks containing genuine uses of one of the
- ;;; Receivers. Exits are excluded, since they don't drop through to the
- ;;; receiver.
- ;;;
- (defun find-values-generators (receivers)
- (declare (list receivers))
- (collect ((res nil adjoin))
- (dolist (rec receivers)
- (dolist (pop (ir2-block-popped (block-info rec)))
- (do-uses (use pop)
- (unless (exit-p use)
- (res (node-block use))))))
- (res)))
-
-
- ;;; Stack-Analyze -- Interface
- ;;;
- ;;; Analyze the use of unknown-values continuations in Component, inserting
- ;;; cleanup code to discard values that are generated but never received. This
- ;;; phase doesn't need to be run when Values-Receivers is null, i.e. there are
- ;;; no unknown-values continuations used across block boundaries.
- ;;;
- ;;; Do the backward graph walk, starting at each values receiver. We ignore
- ;;; receivers that already have a non-null Start-Stack. These are nested
- ;;; values receivers that have already been reached on another walk. We don't
- ;;; want to clobber that result with our null initial stack.
- ;;;
- (defun stack-analyze (component)
- (declare (type component component))
- (let* ((2comp (component-info component))
- (receivers (ir2-component-values-receivers 2comp))
- (generators (find-values-generators receivers)))
-
- (dolist (block generators)
- (find-pushed-continuations block))
-
- (dolist (block receivers)
- (unless (ir2-block-start-stack (block-info block))
- (stack-simulation-walk block ())))
-
- (dolist (block generators)
- (annotate-dead-values block))
-
- (do-blocks (block component)
- (let ((top (car (ir2-block-end-stack (block-info block)))))
- (dolist (succ (block-succ block))
- (when (and (block-start succ)
- (not (eq (car (ir2-block-start-stack (block-info succ)))
- top)))
- (discard-unused-values block succ))))))
-
- (undefined-value))
-